package com.chalmers.foa.gamemodel.level.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.xmlpull.v1.XmlPullParserException;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;

import com.chalmers.foa.Main;
import com.chalmers.foa.core.Game;
import com.chalmers.foa.gamemodel.events.EventFactory;
import com.chalmers.foa.gamemodel.events.IEvent;
import com.chalmers.foa.gamemodel.events.EventFactory.EventType;
import com.chalmers.foa.gamemodel.gameobjects.IGameObject;
import com.chalmers.foa.gamemodel.gameobjects.IGameObjectEventCastable;
import com.chalmers.foa.gamemodel.gameobjects.GameObjectFactory;
import com.chalmers.foa.gamemodel.gameobjects.GameObjectFactory.ObjectType;
import com.chalmers.foa.gamemodel.gameobjects.effects.impl.Particle;
import com.chalmers.foa.gamemodel.gameobjects.other.PlayerModel;
import com.chalmers.foa.gamemodel.gameobjects.other.impl.DoorIntent;
import com.chalmers.foa.gamemodel.gameobjects.other.impl.PlayerObject;
import com.chalmers.foa.gamemodel.level.ILevel;
import com.chalmers.foa.gui.menus.SelectLevel;
import com.chalmers.foa.gui.menus.Settings;

public class Level implements ILevel {
	private List<IGameObject> gameObjects;
	private IGameObject[] gameObjectArray;
	private PlayerModel player;
	private List<Particle> particles;
	private List<IEvent> events;
	private String levelIntroText;
	
	public Level(int _level,Context _con) throws LevelNotLoadedException{
		XmlLevelLoader.getInstance().setVariables(_con, _level);
		gameObjects = new ArrayList<IGameObject>(XmlLevelLoader.getInstance().getGameObjects());
		events = new ArrayList<IEvent>(XmlLevelLoader.getInstance().getEvents());
		//Finds out if any object is bound with an other object
		for(IGameObject object:gameObjects){
			if(object.getBoundID()!=-1){
				object.bindObject(gameObjects.get(object.getBoundID()),null);
			}
			//Finds out if there is any event, and what object (if there is any, is bound to it)
			if(object instanceof IGameObjectEventCastable){
				for(IEvent event:events){
					if(event.getBoundID()==object.getID()){
						((IGameObjectEventCastable)object).bindEvent(event);
					}
				}
			}
			else if(object instanceof PlayerObject && player==null)
				player = new PlayerModel(object);
				
		}
		//Sorts the Objects into the correct layers
		Collections.sort(gameObjects);
		//Creates an array because it's faster to read from than an list
		gameObjectArray = new IGameObject[0];
		gameObjectArray = (IGameObject[])gameObjects.toArray(gameObjectArray);
		particles = new ArrayList<Particle>();
		levelIntroText = XmlLevelLoader.getInstance().getLevelIntro();
		//Removes the levelLoader and calls Garbage collect to remove the allocated memory
		//Extremely useful to do between levels so that the GC doesn't kick in
		//whilst the player is playing.
		System.gc();
	}
	public List<IGameObject> getGameObjects(){
		return gameObjects;
	}
	public IGameObject[] getGameObjectsAsArray() {
		return gameObjectArray;
	}
	public PlayerModel getPlayer() {
		return player;
	}
	public List<Particle> getParticles(){
		return particles;
	}
	public void addParticles(List<Particle> _particle){
		particles.addAll(_particle);
	}
	public String getLevelIntro(){
		return levelIntroText;
	}
	public List<IEvent> getGameEvents(){
		return events;
	}
	
	private static class XmlLevelLoader {
		private static XmlLevelLoader instance;
		private ArrayList<IGameObject> objects;
		private ArrayList<IEvent> events;
		private String levelIntro;
		
		public static XmlLevelLoader getInstance(){
			if(instance==null){
				instance= new XmlLevelLoader();
			}
			return instance;
		}
		protected void setVariables(Context _con,int _level) throws LevelNotLoadedException{
			loadObjects(_level,_con);
			loadEvents(_level, _con.getResources());	
		}
		public List<IEvent> getEvents(){
			return events;
		}
		public List<IGameObject> getGameObjects(){
			return objects;
		}
		public String getLevelIntro(){
			return levelIntro;
		}
		private ArrayList<IGameObject> loadObjects(int _level,Context con) throws LevelNotLoadedException{
			try{
				objects = new ArrayList<IGameObject>();
				events = new ArrayList<IEvent>();
				XmlResourceParser xrp = con.getResources().getXml(_level);//_level
				while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
					IGameObject object = null;
					if (xrp.getEventType() == XmlResourceParser.START_TAG) {
						String s = xrp.getName();
						if (s.equals("objectBit")) {
                              int resId = xrp.getAttributeResourceValue(null, "bitmap", 0);
                        	  Bitmap bitmap = BitmapFactory.decodeResource(con.getResources(),resId);
                        	  ObjectType type = ObjectType.valueOf(xrp.getAttributeValue(null, "objectType"));
                        	  int id = xrp.getAttributeIntValue(null, "id", -1);
                        	  int layer = xrp.getAttributeIntValue(null, "layer", 3);
                        	  int boundID = xrp.getAttributeIntValue(null, "bind", -1);

                              object = GameObjectFactory.createBitmapObject(type,bitmap, getPosition(xrp),layer,boundID);
                              object.setID(id);
                              if(object instanceof DoorIntent){
                            	  Class <?> intentClass;
                            	  if(xrp.getAttributeValue(null, "intent").equals("Game"))
                            		  intentClass=Game.class;
                            	  else if(xrp.getAttributeValue(null, "intent").equals("SelectLevel"))
                            		  intentClass=SelectLevel.class;
                            	  else if(xrp.getAttributeValue(null, "intent").equals("Settings"))
                            		  intentClass=Settings.class;
                            	  else
                            		  intentClass=Main.class;
                            	  ((DoorIntent)object).setIntent(con, intentClass);
                            	  
                            	  if(intentClass.equals(Main.class))
                            		  ((DoorIntent)object).getIntent().putExtra("value", "Exit");
                            	  else if(intentClass.equals(Game.class))
                            		  ((DoorIntent)object).getIntent().putExtra("value", "Game");
                            	  else if(intentClass.equals(SelectLevel.class))
                            		  ((DoorIntent)object).getIntent().putExtra("value", "SelectedLevel");
                            	  else if(intentClass.equals(Settings.class))
                            		  ((DoorIntent)object).getIntent().putExtra("value", "Settings");
                              }
                         }
                    } else if (xrp.getEventType() == XmlResourceParser.END_TAG) {
                         ;
                    } else if (xrp.getEventType() == XmlResourceParser.TEXT) {
                         levelIntro =xrp.getText();
                    }
                    xrp.next();
                    if(object!=null)
                    	objects.add(object);
				}
				xrp.close();
				return objects;

				} catch (XmlPullParserException xppe) {
	        	  throw new LevelNotLoadedException();
				} catch (IOException ioe) {
	        	  throw new LevelNotLoadedException();
				}
		}
		private ArrayList<IEvent> loadEvents(int _level,Resources res) throws LevelNotLoadedException{
			try{
				events = new ArrayList<IEvent>();
				XmlResourceParser xrp = res.getXml(_level);//_level
				while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
					IEvent event = null;
					if (xrp.getEventType() == XmlResourceParser.START_TAG) {
						String s = xrp.getName();
						if (s.equals("event")) {
							EventType type = EventType.valueOf(xrp.getAttributeValue(null, "eventType"));
                      	  	int boundID = xrp.getAttributeIntValue(null, "bind", -1);
                      	  	boolean create = xrp.getAttributeBooleanValue(null, "create", false);
							List<IGameObject> subObjects = new ArrayList<IGameObject>();
							String pS = xrp.getAttributeValue(null, "objects");
							try{
					            String pSx = pS.substring(0, pS.indexOf(","));
					            String pSy = pS.substring(pSx.length()+1,pS.length());
					            subObjects.add(this.objects.get(Integer.valueOf(pSx)));
								while(true){
									try{
										pSx = pSy.substring(0, pSy.indexOf(","));
										subObjects.add(this.objects.get(Integer.valueOf(pSx)));
							            pSy = pSy.substring(pSx.length()+1,pSy.length());
									}
									catch(Exception e){
										subObjects.add(this.objects.get(Integer.valueOf(pSy)));
										break;
									}
								}
							}
							catch(StringIndexOutOfBoundsException e){
								subObjects.add(objects.get(Integer.valueOf(pS)));
							}
							
							event = EventFactory.getEvent(type,boundID,create, subObjects);
                         }
                    } else if (xrp.getEventType() == XmlResourceParser.END_TAG) {
                         ;
                    } else if (xrp.getEventType() == XmlResourceParser.TEXT) {
                         levelIntro =xrp.getText();
                    }
                    xrp.next();
                    if(event!=null)
                    	events.add(event);
				}
				xrp.close();
				return events;

				} catch (XmlPullParserException xppe) {
	        	  throw new LevelNotLoadedException();
				} catch (IOException ioe) {
	        	  throw new LevelNotLoadedException();
				}
		}
		public Point getPosition(XmlResourceParser _xrp){
            String pS = _xrp.getAttributeValue(null, "position");
            String pSx = pS.substring(0, pS.indexOf(","));
            String pSy = pS.substring(pSx.length()+1,pS.length());
            return new Point(Integer.valueOf(pSx),Integer.valueOf(pSy));
		}
		
	}
}
